/**
 * React 官方推荐的“错误边界”实现方式。
 * React 官方建议通过类组件的 componentDidCatch 和 getDerivedStateFromError 生命周期来捕获渲染、生命周期和构造函数中的错误。
 */
import { Button, Result } from 'antd';
import { Component, type ReactNode } from 'react';

import { BizError } from '@/utils';

import { message } from '../AntdGlobal';

/**
 * ErrorBoundaryProps
 * @description 定义错误边界组件的 props 类型，children 表示需要被错误边界包裹的子组件
 */
interface ErrorBoundaryProps {
    children?: ReactNode;
}

/**
 * ErrorBoundaryState
 * @description 组件内部 state，hasError 标记是否捕获到错误
 */
interface ErrorBoundaryState {
    hasError: boolean;
}

/**
 * ErrorBoundary 错误边界组件
 * 防止整个应用崩溃，并显示友好的错误提示页面。
    1、React 组件树中的渲染错误
        包括子组件的 render 方法、生命周期方法（如 componentDidMount、componentDidUpdate）、构造函数中的 JavaScript 错误。
        通过 React 的 componentDidCatch 和 getDerivedStateFromError 生命周期自动捕获。
    2、全局 JavaScript 错误
        通过 window.addEventListener('error', ...) 监听到的运行时 JS 错误（如未捕获的异常、脚本加载错误等）。
        例如：代码抛出 throw new Error('xxx') 或第三方库报错。
    3、全局未处理的 Promise 拒绝
        通过 window.addEventListener('unhandledrejection', ...) 监听到的未被 catch 的 Promise 错误。
        例如：Promise.reject('xxx') 且没有 catch。

    不能捕获的情况：
    1、事件处理函数中的错误（如 onClick 内部抛错，需自己 try/catch）。
    2、服务端渲染（SSR）阶段的错误。
    3、组件外部的异步回调（如 setTimeout、setInterval）中的错误。
 */
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
    // 初始化 state，默认没有错误
    state: ErrorBoundaryState = { hasError: false };

    /**
     * 返回首页的处理函数
     * 会将 hash 跳转到首页并刷新页面
     */
    goHome = () => {
        window.location.hash = '#/';
        window.location.reload();
    };

    /**
     * React 生命周期静态方法
     * 当子组件抛出错误时，更新 state 以触发降级 UI
     */
    static getDerivedStateFromError() {
        return { hasError: true };
    }

    /**
     * React 生命周期方法
     * 捕获子组件渲染、生命周期等阶段的错误
     * @param error 抛出的错误对象
     * @param info  错误信息
     */
    componentDidCatch(error: Error, info: React.ErrorInfo) {
        // 可以在这里上报错误日志到服务器
        console.error('React 捕获到错误:', error, info);
    }

    /**
     * 组件挂载时，注册全局错误监听
     * 可捕获 window 级别的 JS 错误和未处理的 Promise 拒绝
     */
    componentDidMount() {
        window.addEventListener('error', this.handleWindowError, true);
        window.addEventListener('unhandledrejection', this.handlePromiseRejection, true);
    }

    /**
     * 组件卸载时，移除全局错误监听，防止内存泄漏
     */
    componentWillUnmount() {
        window.removeEventListener('error', this.handleWindowError, true);
        window.removeEventListener('unhandledrejection', this.handlePromiseRejection, true);
    }

    /**
     * 全局 JS 错误处理函数
     * @param event ErrorEvent 对象
     */
    handleWindowError = (event: ErrorEvent) => {
        // 可以在这里上报错误日志到服务器
        console.error('全局错误:', event.message);
        this.setState({ hasError: true });
    };

    /**
     * 全局 Promise 未处理拒绝处理函数
     * 业务异常（如 code 非 000000）只弹 message，不让 ErrorBoundary 降级。
     * 只有真正的代码异常（如 JS 报错、接口 500）才让 ErrorBoundary 兜底。
     * @param event PromiseRejectionEvent 对象
     */
    handlePromiseRejection = (event: PromiseRejectionEvent) => {
        const reason = event.reason as any;
        // 可以在这里上报错误日志到服务器
        console.error('全局Promise拒绝:', reason);

        // 1) 如果是 BizError（业务错误），只提示 message，不降级
        if (reason instanceof BizError) {
            // 已经在请求层展示过的业务错误就不再重复弹窗
            if (!reason.handled && typeof reason.message === 'string' && reason.message) {
                message.error(reason.message);
            }
            return;
        }

        // 2) 兼容性判断：如果 reason 有 code 字段且表明是业务错误，也只提示
        if (reason && reason.code && String(reason.code) !== '000000') {
            const handled = (reason as any).handled;
            if (!handled && typeof reason.message === 'string' && reason.message) {
                message.error(reason.message);
            }
            return;
        }

        // 其它异常降级处理
        this.setState({ hasError: true });
    };

    /**
     * 渲染函数
     * 如果捕获到错误，渲染错误提示页面，否则正常渲染子组件
     */
    render() {
        // 兼容 errorElement 场景：如果没有 children，说明是 errorElement 直接渲染
        if (this.state.hasError || !this.props.children) {
            // 错误时渲染 antd 的 Result 组件，展示错误信息和返回首页按钮
            return (
                <Result
                    status="warning"
                    subTitle="抱歉，页面似乎出错了"
                    extra={
                        <Button
                            type="primary"
                            onClick={this.goHome}
                        >
                            返回首页
                        </Button>
                    }
                />
            );
        }
        // 未捕获到错误时，正常渲染子组件
        return this.props.children;
    }
}

export default ErrorBoundary;
